package video.background.Controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.model.GetObjectRequest;
import com.qcloud.cos.region.Region;
import com.tencent.cloud.Response;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.catalina.util.Strftime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import video.background.Controller.ControllerParams.HandleVoiceParams;
import video.background.Controller.ControllerParams.QueryFeatureListParams;
import video.background.Controller.View.CompareResult;
import video.background.Dao.*;
import org.springframework.web.multipart.MultipartFile;
import video.background.Model.*;
import video.background.Utils.*;
import video.background.Utils.VoiceUtils.*;

import javax.servlet.http.HttpServletResponse;
import javax.swing.plaf.ActionMapUIResource;
import javax.validation.constraints.Null;
import java.awt.font.ShapeGraphicAttribute;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Array;
import java.sql.Struct;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.lang.Integer.parseInt;

/**
 * @program: background
 * @description:
 * @author: hlt
 * @create: 2023-08-11 08:54
 **/
@RestController
@RequestMapping("/voice")
@CrossOrigin(origins = "*", maxAge = 3600)
@Api(tags = "声纹相关接口")
public class VoiceController {

    private static String requestUrl = "https://api.xf-yun.com/v1/private/s782b4996";
    private static String APPID = "5b1c8319";
    private static String apiSecret = "OTNhYjQ1MzMwYmVhNmQwNTJlMTBiMzFk";
    private static String apiKey = "cace5423224c609f5fdfbdbf72fa2dac";

    // TempAudioPath的相对路径
    private static String AUDIO_PATH = "src/main/resources/static/TempAudioFiles";

    // 腾讯COS相关配置
    private static final String bucketName = "smart-box-1311261154";
    private static final String region = "ap-nanjing";

    @Autowired
    private VoiceLibraryDao voiceLibraryDao;
    @Autowired
    private UserDao userDao;
    @Autowired
    private UserGroupDao userGroupDao;
    @Autowired
    private FileTableDao fileTableDao;
    @Autowired
    private OptRecodeDao optRecodeDao;
    @Autowired
    private TeamDao teamDao;
    @Autowired
    private OptResultDao optResultDao;

    @ApiOperation("用户上传自己的声纹信息到团队声纹库,重复上传就是更新")
    @PostMapping(value="/uploadVoice")
    public Result uploadVoice(@RequestHeader String Authorization,
                              @RequestParam Long groupId,
                              @RequestParam long size,
                              @RequestParam("file") MultipartFile file) throws IOException {
        Long userId = JwtUtils.decoderToken(Authorization);
        double Size = ((double) size / (1024 * 1024));
        if(Size > 4.0) return Result.Error("上传的文件大小不能大于4MB", null);
        if(userId == 0 || groupId == 0 || file.isEmpty())
            return Result.Error("数据不全！", null);
        LambdaQueryWrapper<VoiceLibrary> vlqw = new LambdaQueryWrapper<>();
        vlqw.eq(VoiceLibrary::getGroupId, groupId);
        VoiceLibrary voiceLibrary = voiceLibraryDao.selectOne(vlqw);
        if(voiceLibrary == null) return Result.Error("团队尚未创建声纹库！", null);
        String libId = voiceLibrary.getLibId();
        // 判断团队文件是否超出限制
        Team team = teamDao.selectById(groupId);
        if(team == null) return Result.Error("团队信息未找到！", null);
        if(team.getCurrentFileSize() + Size > team.getFileCapacity()) return Result.Error("当前团队的文件容量已达上限！", null);
        team.setCurrentFileSize(team.getCurrentFileSize() + Size);
        teamDao.updateById(team); // 更新团队当前的文件大小
        String featureId = generateUniqueFeatureId();
        // 将file保存在本地
        String path = MultipartFileUtils.saveMultipartFile(file, AUDIO_PATH, userId + "_" + groupId);
        // 校验用户是否重复上传
        LambdaQueryWrapper<FileTable> ftlqw = new LambdaQueryWrapper<>();
        ftlqw.eq(FileTable::getGroupId,groupId).eq(FileTable::getUserId,userId);
        FileTable filet = fileTableDao.selectOne(ftlqw);
        if(filet != null) {
            // 更新用户的声纹信息
            String newLink = TencentCOSUtil.uploadfileFromBin("VoiceFiles",  userId + "_" + groupId + ".mp3", file);
//            System.out.println("file: " + file);
            // 寻找声纹id
            LambdaQueryWrapper<FileTable> ftlqw1 = new LambdaQueryWrapper<>();
            ftlqw1.eq(FileTable::getUserId, userId).eq(FileTable::getGroupId, groupId);
            String featureId1 = fileTableDao.selectOne(ftlqw1).getFeatureId();
            System.out.println(featureId1);
            Map<String, Object> result = UpdateFeature.doUpdateFeature(requestUrl, APPID,
                    apiSecret, apiKey, path, libId, featureId1);
            //记录日志
            Users users = userDao.selectById(userId);
            LambdaQueryWrapper<UserGroup> uglqw = new LambdaQueryWrapper<>();
            uglqw.eq(UserGroup::getGroupId,groupId).eq(UserGroup::getUserId,userId);
            UserGroup userGroup = userGroupDao.selectOne(uglqw);
            filet.setFileSize(size);
            filet.setLink(newLink);
            filet.setUploadTime(DateTimeUtils.dateTimeAndSecondToString());
            fileTableDao.updateById(filet);
            char userRole = userGroup.getUserRole();
            String role = "";
            if (userRole == '0') role = "创始人";
            else if (userRole == '1') role = "管理者";
            OptRecode optRecode = new OptRecode(null,groupId,team.getGroupName(),userId,users.getUserName(),2,"更新声纹信息",
                    DateTimeUtils.localDateTimeAndSecondToString(),userRole,role);
            optRecodeDao.insert(optRecode);
            userGroup.setIsInputVoice('1');
            userGroupDao.updateById(userGroup);
            MessageUtils.sendMessage(userId,userDao.selectById(userId).getUserName(),3,groupId);
            return Result.Success("您的声纹信息已更新！", result);
        }
        // 请求讯飞的接口
        Map<String, Object> result = CreateFeature.doCreateFeature(requestUrl, APPID, apiSecret, apiKey, path, libId, featureId);
        // 将上传声纹的信息保存在数据库
        String fileLink = TencentCOSUtil.uploadfileFromBin("VoiceFiles", userId + "_" + groupId + ".mp3", file);
        FileTable fileTable = new FileTable(featureId,userId,userDao.selectById(userId).getUserName(),
                size,DateTimeUtils.dateTimeToString(),groupId,fileLink);
        fileTableDao.insert(fileTable);
        // 将本地的文件删除
        String deleteResult = MultipartFileUtils.deleteFile(path);
        //记录日志
        Users users = userDao.selectById(userId);
        LambdaQueryWrapper<UserGroup> uglqw = new LambdaQueryWrapper<>();
        uglqw.eq(UserGroup::getUserId,userId).eq(UserGroup::getGroupId,groupId);
        UserGroup userGroup = userGroupDao.selectOne(uglqw);
        // 将用户的声纹信息更新为 已上传
        userGroup.setIsInputVoice('1');
        userGroupDao.updateById(userGroup);
        char userRole = userGroup.getUserRole();
        String role = "";
        if (userRole == '0') role = "创始人";
        else if (userRole == '1') role = "管理者";
        else role = "普通成员";
        OptRecode optRecode = new OptRecode(null,groupId,team.getGroupName(),userId,users.getUserName(),1,"上传声纹信息",
                DateTimeUtils.localDateTimeAndSecondToString(),userRole,role);
        optRecodeDao.insert(optRecode);
        MessageUtils.sendMessage(userId,userDao.selectById(userId).getUserName(),2,groupId);
        return Result.Success("上传成功！", result);
    }

    @ApiOperation("删除声纹信息")
    @PostMapping("/deleteFeature")
    public Result deleteFeature(@RequestHeader String Authorization, @RequestBody HandleVoiceParams data){
        long groupId = data.getGroupId();
        String featureId = data.getFeatureId();
        if(groupId == 0) return Result.Error("数据不全！", null);
        if(data.getFeatureId() == null) return Result.Error("数据不全！", null);
        // 校验权限
        Long userId = JwtUtils.decoderToken(Authorization);
        if(userId == null) return Result.Error("数据不全！,请检查数据", null);
        //找到删除发起人信息
        LambdaQueryWrapper<UserGroup> ulqw = new LambdaQueryWrapper<>();
        ulqw.eq(UserGroup::getUserId, userId).eq(UserGroup::getGroupId,groupId);
        UserGroup userGroup = userGroupDao.selectOne(ulqw);
        LambdaQueryWrapper<FileTable> flqw = new LambdaQueryWrapper<>();
        flqw.eq(FileTable::getFeatureId,featureId);
        FileTable fileTable = fileTableDao.selectOne(flqw);
        LambdaQueryWrapper<VoiceLibrary> vlqw = new LambdaQueryWrapper<>();
        vlqw.eq(VoiceLibrary::getGroupId,groupId);
        String libId = voiceLibraryDao.selectOne(vlqw).getLibId();
        //找到被删除声纹信息的主人
        Users be_user = userDao.selectById(fileTable.getUserId());
        ulqw.eq(UserGroup::getUserId, be_user.getUserId()).eq(UserGroup::getGroupId,groupId);
        UserGroup be_userGroup = userGroupDao.selectOne(ulqw);
        //如果是团队创始人或者是删除自己的声纹信息或者是管理员删除普通用户
        if (userGroup.getUserRole() == '0' || fileTable.getUserId().equals(userId) || (userGroup.getUserRole() == '1' && be_userGroup.getUserRole() == '2')){
            //删除声纹信息
            Map<String, Object> result = DeleteFeature.doDeleteFeature(requestUrl, APPID, apiSecret, apiKey, libId,featureId);
            // 删除存储桶上的文件
            String link = fileTableDao.selectById(featureId).getLink();
            int index = link.lastIndexOf("/");
            String fileName = link.substring(index + 1);
            boolean is_delete = TencentCOSUtil.deleteFile("VoiceFiles", fileName);
            if(!is_delete) return Result.Error500("服务器内部错误，请稍后再试！",null);
            // 更新团队的文件容量
            Team team = teamDao.selectById(groupId);
            team.setCurrentFileSize(team.getCurrentFileSize() - fileTable.getFileSize());
            teamDao.updateById(team);
            // 删除fileTable表里面的数据
            fileTableDao.deleteById(featureId);
            //记录日志
            Users users = userDao.selectById(userId);
            char userRole = userGroup.getUserRole();
            String role = "";
            if (userRole == '0') role = "创始人";
            else if (userRole == '1') role = "管理者";
            OptRecode optRecode = new OptRecode(null,groupId,team.getGroupName(),userId,users.getUserName(),3,"删除声纹信息",
                    DateTimeUtils.localDateTimeAndSecondToString(),userRole,role);
            optRecodeDao.insert(optRecode);
            // 将用户的声纹信息更新为 未上传
            userGroup.setIsInputVoice('0');
            userGroupDao.updateById(userGroup);
            return Result.Success("删除成功！",result);
        }
        return Result.Error("权限不足，请检查数据",null);
    }

    @ApiOperation("用户更新自己的声纹信息")
    @PostMapping("/updateVoice")
    public Result updateVoice(@RequestHeader String Authorization,
                              @RequestParam Long groupId,
                              @RequestParam String featureId,
                              @RequestParam long size,
                              @RequestParam("file") MultipartFile file) throws IOException {
        double fileSize = (double) size / (1024 * 1024);
        if(fileSize >= 4) return Result.Error("上传的文件大小不能大于4MB", null);
        Long userId = JwtUtils.decoderToken(Authorization);
        if(userId ==  null || groupId == null || featureId == null)
            return Result.Error("数据不全！", null);
        FileTable fileTable = fileTableDao.selectById(featureId);
        Team team = teamDao.selectById(groupId);
        if(team.getCurrentMemberSize() - (double)fileTable.getFileSize() / (1024 * 1024) + fileSize > team.getFileCapacity())
            return Result.Error("您更新后的文件大小超过了团队的文件容量",null);
        // 将存储桶上的旧文件删除
//        String link = fileTable.getLink();
//        int index = link.lastIndexOf("/");
//        String filename = link.substring(index + 1);
//        TencentCOSUtil.deleteFile("VoiceFiles",filename);
        // 校验声纹库和libId
        LambdaQueryWrapper<VoiceLibrary> vlqw = new LambdaQueryWrapper<>();
        vlqw.eq(VoiceLibrary::getGroupId,groupId);
        List<VoiceLibrary> voiceLibraries = voiceLibraryDao.selectList(vlqw);
        if(voiceLibraries.isEmpty()) return Result.Error("当前团队尚未创建声纹库！",null);
        String libId = voiceLibraries.get(0).getLibId();
        // 上传新的文件到腾讯云存储桶(直接覆盖了)
        String newLink = TencentCOSUtil.uploadfileFromBin("VoiceFiles",  userId + "_" + groupId + ".mp3", file);
        System.out.println("file: " + file);
        // 调用讯飞的接口
        String path = MultipartFileUtils.saveMultipartFile(file, AUDIO_PATH, userId + "_" + groupId);
        if (path == null)  return Result.Error("您的文件似乎有问题！",null);
        System.out.println("path: " + path);
        Map<String, Object> result
                = UpdateFeature.doUpdateFeature(requestUrl, APPID, apiSecret, apiKey, path, libId, featureId);
        // 将本地的临时文件删除
//        String deleteResult = MultipartFileUtils.deleteFile(path);
//        System.out.println(deleteResult);
        // 修改数据库（团队id相同，因此newlink和link相同，featureId也相同，故不需要修改）
        //记录日志
        Users users = userDao.selectById(userId);
        LambdaQueryWrapper<UserGroup> uglqw = new LambdaQueryWrapper<>();
        uglqw.eq(UserGroup::getGroupId,groupId).eq(UserGroup::getUserId,userId);
        UserGroup userGroup = userGroupDao.selectOne(uglqw);
        char userRole = userGroup.getUserRole();
        String role = "";
        if (userRole == '0') role = "创始人";
        else if (userRole == '1') role = "管理者";
        OptRecode optRecode = new OptRecode(null,groupId,team.getGroupName(),userId,users.getUserName(),2,"更新声纹信息",
                DateTimeUtils.localDateTimeAndSecondToString(),userRole,role);
        optRecodeDao.insert(optRecode);
        userGroup.setIsInputVoice('1');
        userGroupDao.updateById(userGroup);
        MessageUtils.sendMessage(userId,userDao.selectById(userId).getUserName(),3,groupId);
        return Result.Success("更新成功！", result);
    }


    @PostMapping("/OneByOneCompare")
    @ApiOperation("用户上传声纹判断是否自己的声纹信息")
    public Result OneByOneCompare(@RequestHeader String Authorization,
                                  @RequestParam("file")MultipartFile file,
                                  @RequestParam Long groupId) throws IOException {
        Long userId = JwtUtils.decoderToken(Authorization);
        if(userId == null || groupId == 0) return Result.Error("数据不全！", null);
        // 获取featureId
        LambdaQueryWrapper<FileTable> ftlqw = new LambdaQueryWrapper<>();
        ftlqw.eq(FileTable::getUserId,userId).eq(FileTable::getGroupId,groupId);
        FileTable fileTable1 = fileTableDao.selectOne(ftlqw);
        if(fileTable1 == null) return Result.Error("您似乎还未上传声纹信息",null);
        String featureId = fileTable1.getFeatureId();
        String path = MultipartFileUtils.saveMultipartFile(file, AUDIO_PATH, userId + "_" + groupId);
        LambdaQueryWrapper<VoiceLibrary> vlqw = new LambdaQueryWrapper<>();
        vlqw.eq(VoiceLibrary::getGroupId,groupId);
        String libId = voiceLibraryDao.selectOne(vlqw).getLibId();
        Map<String, Object> result = SearchOneFeature.doSearchOneFeature(requestUrl,
                APPID, apiSecret, apiKey, path, libId, featureId);
//        if(!result.get("message").equals("success")) return Result.Error500("内部错误，请求失败！", null);
        double score = Double.parseDouble(result.get("score").toString()) * 100;
        String featureId1 = result.get("featureId").toString();
        FileTable fileTable = fileTableDao.selectById(featureId1);
        if(fileTable == null) return Result.Error500("内部错误，请求失败！",null);
        // 将本地的临时文件删除
        String s = MultipartFileUtils.deleteFile(path);
        if(!s.equals("删除成功！")) System.out.println(DateTimeUtils.dateTimeAndSecondToString() + "删除本地文件失败！");
        LambdaQueryWrapper<UserGroup> uglqw = new LambdaQueryWrapper<>();
        uglqw.eq(UserGroup::getGroupId,groupId).eq(UserGroup::getUserId,userId);
        char userRole = userGroupDao.selectOne(uglqw).getUserRole();
        String roleName = "";
        if(userRole == '0') roleName = "创始人";
        else if (userRole == '1') roleName = "管理员";
        else roleName = "成员";
        // 将结果信息新增到数据库
        OptRecode optRecode = new OptRecode(null, groupId, teamDao.selectById(groupId).getGroupName(), userId, userDao.selectById(userId).getUserName(),
                4, "声纹1:1识别", DateTimeUtils.localDateTimeAndSecondToString(), userRole, roleName);
        optRecodeDao.insert(optRecode);
        OptResult optResult = new OptResult(null, optRecode.getRecodeId(),
                userDao.selectById(userId).getUserName(), score, score > 90 ? "符合" : "不符合");
        optResultDao.insert(optResult);
        CompareResult resultView = new CompareResult(userDao.selectById(userId).getUserName(), score);
        ArrayList<CompareResult> compareResults = new ArrayList<>();
        compareResults.add(resultView);
        return Result.Success("请求成功！", compareResults);
    }

    @PostMapping("/searchFeature")
    @ApiOperation("管理员上传声纹判断是否是团队内的声纹信息")
    public Result searchFeature(@RequestHeader String Authorization,
                                @RequestParam Long groupId,
                                @RequestParam("file")MultipartFile file) throws IOException {
        //解析token
        Long userId = JwtUtils.decoderToken(Authorization);
        if (userId == null || groupId == null ) return Result.Error("数据不全",null);
        //准备工作
        LambdaQueryWrapper<UserGroup> uglqw = new LambdaQueryWrapper<>();
        uglqw.eq(UserGroup::getUserId,userId).eq(UserGroup::getGroupId,groupId);
        UserGroup userGroup = userGroupDao.selectOne(uglqw);
        char userRole = userGroup.getUserRole();
        LambdaQueryWrapper<VoiceLibrary> vlqw = new LambdaQueryWrapper<>();
        vlqw.eq(VoiceLibrary::getGroupId,groupId);
        VoiceLibrary voiceLibrary = voiceLibraryDao.selectOne(vlqw);
        if(voiceLibrary == null) return Result.Error("未查询到声纹库信息！",null);
        String libId = voiceLibrary.getLibId();
        //判断是否上传文件
        LambdaQueryWrapper<FileTable> ftlqw = new LambdaQueryWrapper<>();
        ftlqw.eq(FileTable::getUserId,userId).eq(FileTable::getGroupId,groupId);
        FileTable fileTable1 = fileTableDao.selectOne(ftlqw);
        if (fileTable1 == null ) return Result.Error("未上传文件，请检查数据",null);
        //判断权限
        if (userRole == '2') return Result.Error("权限不足，请检查数据~",null);
        String roleName = "";
        if(userRole == '0') roleName = "创始人";
        else roleName = "管理员";
        //存储文件
        String path = MultipartFileUtils.saveMultipartFile(file, AUDIO_PATH, userId + "_" + groupId);
        //1:N查询
        Map<String, Object> result = SearchFeature.doSearchFeature(requestUrl, APPID, apiSecret, apiKey, path, libId);
        if(!result.get("message").equals("success")) return Result.Error500("内部错误，请求失败！", null);
        //记录日志
        OptRecode optRecode = new OptRecode(null, groupId, teamDao.selectById(groupId).getGroupName(), userId,
                userDao.selectById(userId).getUserName(), 5, "1对多查询", DateTimeUtils.localDateTimeAndSecondToString()
                , userRole, roleName);
        optRecodeDao.insert(optRecode);
        //解析结果数据
        JSONArray scoreList = (JSONArray)result.get("text");
        int size = scoreList.size();
        System.out.println(scoreList);
        ArrayList<CompareResult> compareResults = new ArrayList<>();
        for (int i = 0; i < size; i ++ ){
            JSONObject jsonObject = scoreList.getJSONObject(i);
            String featureId = jsonObject.get("featureId").toString();
            double score = Double.parseDouble(jsonObject.get("score").toString()) * 100;
            FileTable fileTable = fileTableDao.selectById(featureId);
            if(fileTable == null){
                System.out.println("空指针异常！featureId=" + featureId);
            }
            if(fileTable != null)
            {
                compareResults.add(new CompareResult(fileTable.getUserName(), score));
                System.out.println("fileTable.getUserName():" + fileTable.getUserName() + "score" + score);
            }

            //记录操作结果
            OptResult optResult = new OptResult(null, optRecode.getRecodeId(),
                    userDao.selectById(userId).getUserName(), score, score > 90 ? "符合" : "不符合");
            optResultDao.insert(optResult);
        }
        // 将本地的临时文件删除
        String s = MultipartFileUtils.deleteFile(path);
        if(!s.equals("删除成功！")) System.out.println(DateTimeUtils.dateTimeAndSecondToString() + "删除本地文件失败！");
        // 修改userGroup里面的状态
        userGroup.setIsInputVoice('1');
        userGroupDao.updateById(userGroup);
        return Result.Success("请求成功！",compareResults);
    }

    @GetMapping("/getTmpSecret")
    @ApiOperation("前端获取下载文件的临时密钥")
    public Result download(@RequestHeader String Authorization,
                         @RequestParam Long groupId){
        Long userId = JwtUtils.decoderToken(Authorization);
        if(userId == null || groupId == 0) return Result.Error("数据不全！", null);
        Response tempSecret = TencentCOSUtil.getTempSecret();
        HashMap<String, Object> data = new HashMap<>();
        data.put("Bucket",bucketName);
        data.put("Region", region);
        String key = "/voices/VoiceFiles/" + userId + "_" + groupId;
        data.put("Key", key);
        data.put("Sign",true);
        data.put("SecretId", tempSecret.credentials.tmpSecretId);
        data.put("SecretKey", tempSecret.credentials.tmpSecretKey);
        data.put("SessionToken", tempSecret.credentials.sessionToken);
        return Result.Success("请求成功！", data);
    }


    /*相关工具*/
    private boolean judgeIsUnique(String libId){
        VoiceLibrary voiceLibrary = voiceLibraryDao.selectById(libId);
        return voiceLibrary == null;
    }

    private double getMultipartFileSizeByMB(MultipartFile file){
        long size = file.getSize();
        return convertFileSizeToMB(size);
    }

    public static double convertFileSizeToMB(long size) {
        BigDecimal fileSize = new BigDecimal(size);
        BigDecimal oneMB = new BigDecimal(1024 * 1024);
        return fileSize.divide(oneMB, RoundingMode.HALF_UP).doubleValue();
    }

    private String generateUniqueFeatureId(){
        while(true){
            String s1 = Random.generateString(10);
            String s2 = Random.generateString(5);
            if(!judgeFeatureIdUnique(s1+"_"+s2)){
                if(judgeFeatureIdUnique(s2+"_"+s1)){
                    return s2+"_"+s1;
                }
            }else{
                return s1+"_"+s2;
            }
        }
    }
    private boolean judgeFeatureIdUnique(String featureId){
        FileTable fileTable = fileTableDao.selectById(featureId);
        return fileTable == null;
    }
}
